﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Forms;

using LinqKit;

using Preactor.ObjectModel;
using Preactor.ObjectModel.BaseTypes;
using Preactor.ObjectModel.Interfaces;
using Preactor.ObjectModelExtensions.DataContext;

namespace Preactor.ObjectModelExtensions
{
    using Preactor.ObjectModelExtensions.Entities;

    public static class PreactorObjectModelExtensions
    {
        public static DateTime GetPreactorReferenceDate(this PreactorObjectModel preactorObjectModel)
        {
            var preactorDateTime = preactorObjectModel.Preactor.ParseShellString("{DATE}");
            var date = DateTime.Parse(preactorDateTime);

            return date;
        }

        public static DateTime GetTerminatorTime(this PreactorObjectModel pom)
        {
            return pom.PlanningBoard.TerminatorTime;
        }

        public static void SetTerminatorTime(this PreactorObjectModel pom, DateTime date)
        {
            pom.PlanningBoard.TerminatorTime = date;
        }

        /// <summary>
        /// Usage: Pom.GetWorkingPath() + @"\Operations.csv"
        /// </summary>
        /// <param name="pom"></param>
        /// <returns></returns>
        public static string GetWorkingPath(this PreactorObjectModel pom)
        {
            return pom.Preactor.ParseShellString("{PATH}");
        }

        public static string GetConnectionString(this PreactorObjectModel pom)
        {
            return pom.Preactor.ParseShellString("{DB CONNECT STRING}");
        }

        #region CALENDAR

        public static bool IsCalendarWindowOpen(this PreactorObjectModel pom)
        {
            // Don't like using exceptions for control flow but there is no API call to find
            // out if a CalendarWindow is Open

            try
            {
                pom.PlanningBoard.LoadCalendarData();
            }
            catch ( Exception )
            {

                return true;
            }

            return false;
        }

        public static void DeleteAllCalendarExceptions(this PreactorObjectModel pom)
        {
            // http://www.albahari.com/nutshell/linqkit.aspx
            // http://www.albahari.com/nutshell/predicatebuilder.aspx

            var isException = PredicateBuilder.True<PrimaryCalendarPeriod>().And( x => x.IsException );

            DeleteCalendarExceptions(pom, isException);
        }

        public static void DeleteCalendarExceptions(this PreactorObjectModel pom, Expression<Func<PrimaryCalendarPeriod, bool>> predicate, bool loadCalendarData = true, bool checkCalendarWindowOpen = true)
        {
            // Make sure all calendar windows are shut
            if (checkCalendarWindowOpen && IsCalendarWindowOpen(pom))
            {
                MessageBox.Show("Please close all calendar windows before deleting calendar exceptions.");
                return;
            }

            predicate = predicate.And( x => x.IsException );

            // Get the connection string
            string connectionString = pom.GetConnectionString();

            using (var dc = new PreactorDataContext(connectionString))
            {
                dc.Connection.Open();

                var exceptions = dc.PrimaryCalendarPeriods.Where( predicate );

                dc.PrimaryCalendarPeriods.DeleteAllOnSubmit(exceptions);
                dc.SubmitChanges();

                dc.Connection.Close();
            }

            if(loadCalendarData)
            {
                pom.PlanningBoard.LoadCalendarData();
            }
        }

        public static List<string> GetCalendarStateNames(this PreactorObjectModel pom)
        {
            string connectionString = pom.GetConnectionString();

            var calendarStateNames = new List<string>();

            using (var dc = new PreactorDataContext(connectionString))
            {
                calendarStateNames = dc.CalendarStates.Select(x => x.Name).ToList();
            }

            return calendarStateNames;
        }

        public static List<CalendarPeriod> GetCalendarPeriods(this PreactorObjectModel pom,
            DateTime startDate,
            DateTime endDate)
        {
            var calendarPeriods = new List<CalendarPeriod>();

            foreach (var resource in pom.GetAllRecords<IResource>())
            {
                var calendarChanges = pom.PlanningBoard.GetCalendarStateChanges(resource.Name, startDate, endDate);

                for (int idx = 0; idx < calendarChanges.Count - 1; idx++)
                {
                    var calendarPeriod = new CalendarPeriod(
                        calendarChanges[idx].CurrentState.Efficiency.ToString(),
                        resource.Name,
                        calendarChanges[idx].ChangeTime,
                        calendarChanges[idx + 1].ChangeTime);

                    calendarPeriods.Add(calendarPeriod);
                }
            }

            return calendarPeriods;
        }

        #endregion

        // =======================================================================================
        // OPERATION

        /// <summary>
        /// Create an order from product details, and creates all child steps if any.
        /// </summary>
        /// <param name="preactorObjectModel"></param>
        /// <param name="partNumber"></param>
        /// <param name="orderNumber"></param>
        /// <returns>Parent record number</returns>
        public static int CreateOrderFromProduct(this PreactorObjectModel preactorObjectModel, string partNumber, string orderNumber)
        {

            //var operation = preactorObjectModel.CreateRecord<IOperation>() as Operation;

            //operation.OrderNumber = orderNumber;
            //operation.PartNumber = partNumber;

            //return preactorObjectModel.Preactor.ExpandJob(operation.FormatNumber, operation.RecordNumber);


            IPreactor preactor = preactorObjectModel.Preactor;

            int orderFormatNumber = preactorObjectModel.GetFormatNumber<IOperation>();
            var products = preactorObjectModel.GetAllRecords<IProduct>().Where(x => x.GetFieldValue<string>("Part No.") == partNumber).OrderBy(x => x.OperationNumber).ToList();

            if (products.Count < 1)
            {
                throw new ArgumentException(String.Format("The part number: '{0}' does not exist in the Products database. \n Check spelling.", partNumber));
            }

            var recordNumbers = new List<int>();

            foreach (IProduct product in products)
            {
                int newRecordNumber = preactor.CreateRecord(orderFormatNumber);
                var op = preactorObjectModel.GetRecord<IOperation>(newRecordNumber) as Operation;

                preactor.WriteField(orderFormatNumber, "Belongs to Order No.", newRecordNumber, product.ParentPart == "PARENT" ? "PARENT" : orderNumber);

                op.OrderNumber = orderNumber;
                op.OperationNumber = product.OperationNumber;
                op.PartNumber = product.GetFieldValue<string>("Part No.");

                //product.SetFieldValue("Operation Name", product.GetFieldValue<string>("Operation Name"));
                //product.SetFieldValue("Process Time Type", product.GetFieldValue<string>("Process Time Type"));
                //product.SetFieldValue("Batch Time", product.GetFieldValue<TimeSpan>("Batch Time").TotalDays);

                // TODO: Add all the time fields, rate per hour, op. time per item etc...

                // ----------------------------------------------------------------------
                // Find out if the "material control complete" classification exists.
                // If it does set MaterialControlComplete to true.
                //FormatFieldPair? ffpMaterialControlComplete = preactor.FindFirstClassificationString("MATERIAL CONTROL COMPLETE");

                //if (ffpMaterialControlComplete.HasValue)
                //{
                //    preactor.WriteField(ffpMaterialControlComplete.Value, newRecordNumber, true);
                //}

                preactor.EvaluateRecord(orderFormatNumber, newRecordNumber);
                recordNumbers.Add(newRecordNumber);
            }

            return recordNumbers.First();
        }

        /// <summary>
        ///  Get all string values corresponding to format field pair. I.e. a string field in a given Preactor format.
        /// </summary>
        /// <param name="preactorObjectModel"></param>
        /// <param name="formatFieldPair"></param>
        /// <returns></returns>
        public static IEnumerable<string> GetAllStringValues(this PreactorObjectModel preactorObjectModel, FormatFieldPair formatFieldPair)
        {
            var collection = new List<string>();

            int recordCount = preactorObjectModel.Preactor.RecordCount(formatFieldPair.FormatNumber);

            for (int record = 1; record <= recordCount; record++)
            {
                collection.Add(preactorObjectModel.Preactor.ReadFieldString(formatFieldPair, record));
            }

            return collection;
        }

        public static List<T> GetOperationsInOrder<T>(this PreactorObjectModel preactorObjectModel, string orderNumber) where T : class, IOperation
        {
            FormatFieldPair formatFieldPair = preactorObjectModel.GetFormatFieldPair<IOperation>(x => x.OrderNumber);
            return preactorObjectModel.GetMatchingCollection(formatFieldPair, orderNumber, GetOperation<T> );
        }

        public static IEnumerable<T> GetOperationsOnResource<T>(this PreactorObjectModel preactorObjectModel, string resourceName) where T : class, IOperation
        {
            FormatFieldPair formatFieldPair = preactorObjectModel.GetFormatFieldPair<IOperation>(x => x.ResourceName);
            return preactorObjectModel.GetMatchingCollection(formatFieldPair, resourceName, GetOperation<T> ).OrderBy(op => op.SetupStartTime);
        }

        // =======================================================================================
        // RECORD
        private static List<T> GetMatchingCollection<T>(this PreactorObjectModel preactorObjectModel, FormatFieldPair formatFieldPair, string fieldValue, Func<PreactorObjectModel, int, T> func) where T : IPreactorRecord
        {
            int record = 0;
            var records = new List<int>();
            var collection = new List<T>();

            int formatNumber = formatFieldPair.FormatNumber;
            int fieldNumber = formatFieldPair.FieldNumber;

            record = preactorObjectModel.Preactor.FindMatchingRecord(formatNumber, fieldNumber, record, fieldValue, SearchDirection.Forwards);

            if (record > 0)
            {
                records.Add(record);

                while (record > 0)
                {
                    record = preactorObjectModel.Preactor.FindMatchingRecord(formatNumber, fieldNumber, record, fieldValue, SearchDirection.Forwards);
                    records.Add(record);
                }

                // remove last entry since it would be less than 0
                records.RemoveAt(records.Count - 1);

                // Create new records and add them to the list
                collection.AddRange(records.Select(r => func(preactorObjectModel, r)));
            }

            return collection;
        }

        private static T GetOperation<T>(this PreactorObjectModel preactorObjectModel, int recordNumber) where T : class, IOperation
        {
            return preactorObjectModel.GetRecord<IOperation>(recordNumber) as T;
        }

        private static T GetResource<T>(this PreactorObjectModel preactorObjectModel, int recordNumber) where T : class, IResource
        {
            return preactorObjectModel.GetRecord<IResource>(recordNumber) as T;
        }

        // =======================================================================================
        // OTHER

        public static IOperation GetLastOperationOnResource(this PreactorObjectModel preactorObjectModel, string resourceName)
        {
            return preactorObjectModel.GetPreviousOperationOnResource(resourceName, preactorObjectModel.PlanningBoard.ScheduleHorizon.End);
        }

        public static IOperation GetPreviousOperationOnResource(this PreactorObjectModel preactorObjectModel, string resourceName, DateTime dateTime)
        {
            var resourceRecord = preactorObjectModel.PlanningBoard.GetResourceNumber(resourceName);
            var operationRecord = preactorObjectModel.PlanningBoard.GetResourcesPreviousOperation(resourceRecord, dateTime);

            if (operationRecord > 0)
            {
                return preactorObjectModel.GetRecord<IOperation>(operationRecord);
            }

            return null;
        }

        public static DateTime GetNextAvailableStartTime(this PreactorObjectModel preactorObjectModel, string resourceName)
        {
            var lastOperation = preactorObjectModel.GetLastOperationOnResource(resourceName);

            // Account for the fact that the last operation could finish well before the terminator line.

            if (lastOperation == null || lastOperation.EndTime < preactorObjectModel.GetTerminatorTime())
            {
                return preactorObjectModel.GetTerminatorTime();
            }

            return lastOperation.EndTime;
        }

        public static DateTime GetNextAvailableStartTime(this PreactorObjectModel preactorObjectModel, Operation lastOperation)
        {
            // Account for the fact that the last operation could finish well before the terminator line.

            if (lastOperation == null || lastOperation.EndTime < preactorObjectModel.GetTerminatorTime())
            {
                return preactorObjectModel.GetTerminatorTime();
            }

            return lastOperation.EndTime;
        }



    }
}